#include "gtest/gtest.h"

#include "ObjectTest.h"
#include "helper/ScopedMemPtr.h"

#include <iostream>

class ArrayTest :  public ObjectTest { 
public:
    void SetUp() override {
        ObjectTest::SetUp();
        ASSERT_TRUE(getObjectFactory() != nullptr);
        array = getObjectFactory()->createArray();
    }
    void TearDown() override {
        delete array;
        ObjectTest::TearDown();
    }
    helper::object::Array * getArray() {
        return array;
    }
    template<typename T, typename Getter>
    void __testComposedTypeGetAndAppend(T * obj, const Getter & getter){
        ASSERT_EQ(0ul, getArray()->size());

        getArray()->append(obj);

        ASSERT_EQ(1ul, getArray()->size());
        T * value = getter(getArray(), 0);
        ASSERT_TRUE(value != nullptr);
        ASSERT_EQ(obj, value);

        getArray()->clear();
        ASSERT_EQ(0ul, getArray()->size());

        //not exist 
        ASSERT_TRUE(nullptr == getter(getArray(), 0));
    }
    template<typename R, typename Getter>
    void __testRawTypeAppendAndGet(const R & expect, const Getter & getter){
        ASSERT_EQ(0ul, getArray()->size());

        getArray()->append(expect);

        ASSERT_EQ(1ul, getArray()->size());
        std::pair<bool, R> value = getter(getArray(), 0);
        ASSERT_TRUE(value.first);
        ASSERT_EQ(value.second, expect);

        getArray()->clear();
        ASSERT_EQ(0ul, getArray()->size());

        //not exist 
        ASSERT_FALSE(getter(getArray(), 0).first);
    }

    template<typename R, typename Appender>
    void __testStdStringAppendAndGet(R expect, const Appender & appender){
        ASSERT_EQ(0ul, getArray()->size());

        appender(getArray());
        ASSERT_EQ(1ul, getArray()->size());

        std::pair<bool, std::string> value = getArray()->getStdString(0);
        ASSERT_TRUE(value.first);
        ASSERT_EQ(value.second, expect);

        getArray()->clear();
        ASSERT_EQ(0ul, getArray()->size());

        //not exist 
        ASSERT_FALSE(getArray()->getStdString(0).first);
    }

private:
    helper::object::Array * array;
};

using namespace std;
using namespace helper;
using namespace object;

using helper::ScopedMemPtr;

TEST_F(ArrayTest, testInitStatus){
    ASSERT_NE(nullptr, getArray());
    ASSERT_EQ(0ul, getArray()->size());
}

TEST_F(ArrayTest, testGetEmptyArray) {
    auto * value = getArray()->get(0);
    ASSERT_EQ(nullptr, value);
}

TEST_F(ArrayTest, testAppendAndGet) {
    auto * expect = getObjectFactory()->createString("hello");
    getArray()->append(expect);
    ASSERT_EQ(1ul, getArray()->size());

    auto * value = getArray()->get(0);
    ASSERT_NE(nullptr, value);
    ASSERT_EQ(expect->toString(), (*value)->toString());
}
TEST_F(ArrayTest, testAppendAndRemove){
    auto * expect = getObjectFactory()->createInteger(10);
    getArray()->append(expect);
    ASSERT_EQ(1ul, getArray()->size());

    auto * value = getArray()->remove(0);
    ASSERT_EQ(0ul, getArray()->size());

    ASSERT_EQ(expect->toString(), value->toString());
    delete value;
}

TEST_F(ArrayTest, testAppendNull) {
    getArray()->append((Object *)nullptr); 
    ASSERT_EQ(0ul, getArray()->size());

    const Array::ValueType * value = getArray()->get(0);
    ASSERT_EQ(nullptr, value);
}

TEST_F(ArrayTest, testTraversal) {
    String * expectString = getObjectFactory()->createString("hello");
    getArray()->append(expectString);
    getArray()->append((Object*)nullptr); 
    ASSERT_EQ(1ul, getArray()->size());

    auto it = getArray()->iterator();
    while(it->hasNext()) {
        const Array::ValueType & value = it->next();

        if (nullptr != value) {
            ASSERT_EQ(expectString->getType(), value->getType());
        }
    }
}

TEST_F(ArrayTest, testNextOutOfRange) {
    ObjectTest::testNextOutOfRange(getArray());
}

TEST_F(ArrayTest, testClear){
    String * expectString = getObjectFactory()->createString("hello");
    getArray()->append(expectString);
    ASSERT_EQ(1ul, getArray()->size());
    
    ObjectTest::testClear(getArray());
}

TEST_F(ArrayTest, testByteAppendAndGet){
    const char expect = 'c';
    __testRawTypeAppendAndGet(expect, [](Array * array, const int index){
            return array->getByte(index);
        });
}
TEST_F(ArrayTest, testIntAppendAndGet){
    const int expect = 1;
    __testRawTypeAppendAndGet(expect, [](Array * array, const int index){
            return array->getInt(index);
        });

    __testRawTypeAppendAndGet((long long)expect, [](Array * array, const int index){
            return array->getLongLong(index);
        });

    __testRawTypeAppendAndGet((double)expect, [](Array * array, const int index){
            return array->getDouble(index);
        });
}

TEST_F(ArrayTest, testAppendAndGetHashElement){
    auto getter = [](Array * array, const int index) {
        return array->getHash(index);
    };
    __testComposedTypeGetAndAppend(getObjectFactory()->createHash(), getter);
}
TEST_F(ArrayTest, testAppendAndGetArrayElement){
    auto getter = [](Array * array, const int index) {
        return array->getArray(index);
    };
    __testComposedTypeGetAndAppend(getObjectFactory()->createArray(), getter);
}

TEST_F(ArrayTest, testAppendAndGetStringElement){
    auto getter = [](Array * array, const int index) {
        return array->getString(index);
    };
    __testComposedTypeGetAndAppend(getObjectFactory()->createString("hello"), getter);
}

TEST_F(ArrayTest, testStdStringAppendAndGet){
    const char * expect  = "cccccccc";
    __testStdStringAppendAndGet(expect, [expect](Array * array){ 
        array->append(expect);
    });

    __testStdStringAppendAndGet(expect, [expect](Array * array){ 
        array->append(expect, strlen(expect));
    });
    __testStdStringAppendAndGet(expect, [expect](Array * array){
        array->append(expect, 0, strlen(expect));
    });
    __testStdStringAppendAndGet(expect, [expect](Array * array){ 
        array->append(strlen(expect), 'c');
    });

    __testStdStringAppendAndGet(std::string(expect), [expect](Array * array){ 
        array->append(std::string(expect));
    });
}
